package com.ctsi.framework.common.startrun;

import com.ctsi.commons.util.BeanComparator;
import com.ctsi.commons.util.ObjectType;
import com.ctsi.commons.util.UtilValidate;
import com.ctsi.commons.util.crypto.HashCrypt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.support.AbstractRefreshableApplicationContext;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import java.sql.Driver;
import java.sql.DriverManager;
import java.util.*;

/**
 * @author: zhangjw
 * @Date: 2018/11/6 13:19
 * @Description: WAR包运行方式下有效
 */
public class StartPlatform extends ContextLoaderListener {

    private static final Logger log = LoggerFactory.getLogger(StartPlatform.class);
    private static final List<IStartRun> runList = new ArrayList<>();
    private static final List<LazyStartRun> lazeRunList = new ArrayList<>();
    private static volatile boolean normalStart = true;
    /**
     * true允许自动覆盖，false抛出异常
     */
    private boolean allowBeanDefinitionOverriding = false;

    public static boolean isNormalStart() {
        return normalStart;
    }

    public static void setNormalStart(boolean normalStart) {
        StartPlatform.normalStart = normalStart;
    }

    public static final void runAutoStart(WebApplicationContext wa, ServletContext servletContext) {
        initRunList(wa);

        log.info("start run start");
        for (IStartRun ir : runList) {
            if (ir.isRun()) {
                log.debug("{} is  run", ir.getClass().getName());
                try {
                    ir.initialize(servletContext);
                } catch (Exception e) {
                    log.error("run {} error", ir.getClass().getName());
                    log.error("contextInitialized error", e);
                }
            } else {
                log.debug("{} is not run,isRun is false", ir.getClass().getName());
            }

        }
        log.info("start run end");

        initLazeRunList(wa);

        log.info(" laze start run start");
        for (LazyStartRun ir : lazeRunList) {
            if (ir.isRun()) {
                log.debug("{} is  run", ir.getClass().getName());
                try {
                    ir.initialize(servletContext);
                } catch (Exception e) {
                    log.error("run {} error", ir.getClass().getName());
                    log.error("contextInitialized error", e);
                }
            } else {
                log.debug("{} is not run,isRun is false", ir.getClass().getName());
            }

        }
        log.info(" laze start run end");
        normalStart = true;
    }

    private static void initLazeRunList(WebApplicationContext wa) {
        String[] beanNames = wa.getBeanNamesForType(LazyStartRun.class);
        for (int i = 0; i < beanNames.length; i++) {
            String beanName = beanNames[i];
            LazyStartRun temIStartRun = wa.getBean(beanName, LazyStartRun.class);
            if (!lazeRunList.contains(temIStartRun)) {
                log.debug("auto add {}", temIStartRun.getClass().getName());
                lazeRunList.add(temIStartRun);
            }

        }
        BeanComparator bCom = new BeanComparator("getOrder");
        Collections.sort(lazeRunList, bCom);

    }

    // 这导致,许多bean提前初始化
    private static void initRunList(WebApplicationContext wa) {
        String[] beanNames = wa.getBeanNamesForType(IStartRun.class);
        for (int i = 0; i < beanNames.length; i++) {
            String beanName = beanNames[i];
            IStartRun temIStartRun = wa.getBean(beanName, IStartRun.class);
            log.debug("auto add class {} bean name :{}", temIStartRun.getClass(), beanName);
            runList.add(temIStartRun);
        }
        BeanComparator bCom = new BeanComparator("getOrder");
        Collections.sort(runList, bCom);
    }

    private static String getDigest(Class<?> obj) {
        byte[] data = ObjectType.getClassByte(obj.getName(), Thread.currentThread().getContextClassLoader());
        return HashCrypt.getDigestHash(data);
    }

    /**
     * Initialize the root web application context.
     */
    @Override
    public void contextInitialized(ServletContextEvent event) {
        // spring基本初始化
        super.contextInitialized(event);
        // 执行附加的
        WebApplicationContext wa = WebApplicationContextUtils.getWebApplicationContext(event.getServletContext());
        if (!checkCondition(wa, event)) {
            normalStart = false;
            ComponentFactory.setServletContext(event.getServletContext());
            log.error("at least one condition not satisfy ,auto run ,not run");
            return;
        }

        runAutoStart(wa, event.getServletContext());

    }

    private boolean checkCondition(WebApplicationContext wa, ServletContextEvent event) {
        String[] beanNames = wa.getBeanNamesForType(IstartPltFormCheck.class);
        if (UtilValidate.isEmpty(beanNames)) {
            return true;
        }
        log.info("run check size {}", beanNames.length);
        List<IstartPltFormCheck> checkList = new ArrayList<IstartPltFormCheck>(beanNames.length);
        for (int i = 0; i < beanNames.length; i++) {
            String beanName = beanNames[i];
            IstartPltFormCheck temIStartRun = (IstartPltFormCheck) wa.getBean(beanName);
            if (!checkList.contains(temIStartRun)) {
                log.debug("auto add {}", temIStartRun.getClass().getName());
                checkList.add(temIStartRun);
            }
        }
        BeanComparator bCom = new BeanComparator("getOrder");
        Collections.sort(checkList, bCom);

        for (IstartPltFormCheck ir : checkList) {

            String className = ir.getClass().getName();
            log.debug("{} is  run", className);
            try {
                if (ir.isRun()) {
                    if (!ir.checkCondition(event.getServletContext())) {
                        log.info("{} return false", className);
                        return false;
                    }
                } else {
                    log.debug("{} is not run,isRun is false", className);
                }
            } catch (Exception e) {
                log.error("run {} error", className);
                log.error("contextInitialized error", e);
                return false;
            }
        }
        return true;
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        for (int i = runList.size() - 1; i > -1; i--) {
            IStartRun ir = runList.get(i);
            // 全部销毁
            if (ir.isRun()) {
                log.debug("{} is  destroy", ir.getClass());
                try {
                    ir.destroy();
                } catch (Throwable e) {
                    log.error("destroy {} error", ir.getClass());
                    log.error("destroy error", e);
                }
            }
            runList.set(i, null);
        }
        runList.clear();
        for (int i = lazeRunList.size() - 1; i > -1; i--) {
            LazyStartRun ir = lazeRunList.get(i);
            // 全部销毁
            if (ir.isRun()) {
                log.debug("{} is  destroy", ir.getClass());
                try {
                    ir.destroy();
                } catch (Throwable e) {
                    log.error("destroy {} error", ir.getClass());
                    log.error("destroy error", e);
                }
            }
            lazeRunList.set(i, null);

        }
        super.contextDestroyed(event);
        clearDrivers();
        destroyThreadsInfo();
    }

    protected void clearDrivers() {
        Enumeration<Driver> drivers = DriverManager.getDrivers();
        while (drivers.hasMoreElements()) {
            Driver driver = drivers.nextElement();
            try {
                DriverManager.deregisterDriver(driver);
                log.info(String.format("deregistering jdbc driver: %s", driver));
            } catch (Exception e) {
                log.warn(String.format("Error deregistering driver %s", driver), e);
            }
        }
    }

    private void destroyThreadsInfo() {
        final Set<Thread> threads = Thread.getAllStackTraces().keySet();
        for (Thread thread : threads) {
            if (needManualDestroy(thread)) {
                log.info("thread :{} should destroy", thread.getName());
            }
        }
    }

    private boolean needManualDestroy(Thread thread) {
        return !thread.isDaemon();
    }

    @Override
    protected void customizeContext(ServletContext servletContext, ConfigurableWebApplicationContext applicationContext) {
        super.customizeContext(servletContext, applicationContext);
        log.info("allowBeanDefinitionOverriding :{}", allowBeanDefinitionOverriding);
        AbstractRefreshableApplicationContext context = (AbstractRefreshableApplicationContext) applicationContext;
        context.setAllowBeanDefinitionOverriding(allowBeanDefinitionOverriding);
    }

}
